
/**
 * LICENCIA LGPL:
 * 
 * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
 * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
 * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
 * la Licencia, o (a su elección) cualquier versión posterior.
 * 
 * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
 * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
 * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
 * License (LGPL) para más detalles
 * 
 * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
 * junto con esta librería; si no es así, escriba a la Free Software Foundation 
 * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
 * <http://www.gnu.org/licenses/>.
 *
 * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
 */
package es.accv.arangi.base.certificate;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERString;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.x509.X509NameTokenizer;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

import es.accv.arangi.base.ArangiObject;
import es.accv.arangi.base.exception.certificate.CertificateFieldException;
import es.accv.arangi.base.exception.certificate.NormalizeCertificateException;
import es.accv.arangi.base.util.AlternativeNameElement;
import es.accv.arangi.base.util.Util;

/**
 * Clase base de todos los certificados de Arangi. Ofrece métodos para trabajar
 * de forma cómoda con los diferentes campos del certificado.<br><br>
 * 
 * Los certificados permitidos son aquellos que cumplen el estándar definido en
 * la <a href="http://tools.ietf.org/rfc/rfc5280.txt" target="rfc">RFC-5280</a>: x.509 v3.
 * 
 * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
 */
public class Certificate extends ArangiObject{

	/**
	 * Logger de la clase
	 */
	static Logger logger = Logger.getLogger(Certificate.class);
	
	/**
	 * OID del campo OCSP de la extensión AIA
	 */
	public static final String OID_OCSP_AIA_EXTENSION	= "1.3.6.1.5.5.7.48.1";
	
	/**
	 * OID del campo id-at-commonName
	 */
	public static final String OID_ID_AT_COMMONNAME	= "2.5.4.3";
	
	/**
	 * OID del campo Userid
	 */
	public static final String OID_USERID	= "0.9.2342.19200300.100.1.1";
	
    /** BC X509Name contains some lookup tables that could maybe be used here. */
    private static final HashMap oids = new HashMap();

    static {
        oids.put("c", X509Name.C);
        oids.put("dc", X509Name.DC);
        oids.put("st", X509Name.ST);
        oids.put("l", X509Name.L);
        oids.put("o", X509Name.O);
        oids.put("ou", X509Name.OU);
        oids.put("t", X509Name.T);
        oids.put("surname", X509Name.SURNAME);
        oids.put("initials", X509Name.INITIALS);
        oids.put("givenname", X509Name.GIVENNAME);
        oids.put("gn", X509Name.GIVENNAME);
        oids.put("sn", X509Name.SN);
        oids.put("serialnumber", X509Name.SN);
        oids.put("cn", X509Name.CN);
        oids.put("uid", X509Name.UID);
        oids.put("emailaddress", X509Name.EmailAddress);
        oids.put("e", X509Name.EmailAddress);
        oids.put("email", X509Name.EmailAddress);
        oids.put("unstructuredname", X509Name.UnstructuredName); //unstructuredName 
        oids.put("unstructuredaddress", X509Name.UnstructuredAddress); //unstructuredAddress
    }

    private static final String[] dNObjects = {
        "unstructuredaddress", "unstructuredname", "emailaddress", "e", "email", "uid", "cn", "sn", "serialnumber", "gn", "givenname",
        "initials", "surname", "t", "ou", "o", "l", "st", "dc", "c"
    };
    
	/**
	 * Objeto certificado en X.509 al que recubre esta clase
	 */
	protected X509Certificate certificate;
	
	/**
	 * Representación del certificado en ASN1
	 */
	X509CertificateStructure asn1Certificate;
	
	/**
	 * Flag que indica si el certificado está autofirmado
	 */
	private Boolean selfSigned;
	
	/**
	 * Constructor que inicializa la clase mediante un certificado en formato
	 * X.509
	 * 
	 * @param certificate Certificado en formato X.509
	 * @throws NormalizeCertificateException No se ha podido normalizar el certificado para
	 * 	ser usado con el proveedor criptográfico de Arangi
	 */
	public Certificate(X509Certificate certificate) throws NormalizeCertificateException {
		super();
		
		initialize (certificate);
	}
	
	/**
	 * Constructor que inicializa la clase mediante un fichero. El fichero debe contener
	 * un certificado X.509 (tal cual o en formato PEM)
	 * 
	 * @param fileCertificate Fichero que contiene un certificado en formato X.509
	 * @throws NormalizeCertificateException No se ha podido normalizar el certificado para
	 * 	ser usado con el proveedor criptográfico de Arangi
	 * @throws FileNotFoundException El fichero no existe
	 */
	public Certificate(File fileCertificate) throws NormalizeCertificateException, FileNotFoundException {
		super();
		
		//-- Obtener el certificado
		X509Certificate certificate = Util.getCertificate(fileCertificate);
		
		//-- Cargar el certificado
		this.certificate = normalize (certificate);
		
		//-- Inicializar
		initialize(certificate);
	}
	
	/**
	 * Constructor que inicializa la clase a partir de un stream de lectura. El stream debe 
	 * contener un certificado X.509 (tal cual o en formato PEM)
	 * 
	 * @param isCertificate Stream de lectura a un certificado en formato X.509
	 * @throws NormalizeCertificateException No se ha podido normalizar el certificado para
	 * 	ser usado con el proveedor criptográfico de Arangi
	 */
	public Certificate(InputStream isCertificate) throws NormalizeCertificateException {
		super();
		
		//-- Obtener el certificado
		X509Certificate certificate = Util.getCertificate(isCertificate);
		
		//-- Cargar el certificado
		this.certificate = normalize (certificate);
		
		//-- Inicializar
		initialize(certificate);
	}
	
	/**
	 * Constructor que inicializa la clase a partir de un array de bytes. Los bytes deben 
	 * ser el contenido de un certificado X.509 (tal cual o en formato PEM)
	 * 
	 * @param contenidoCertificado Contenido de un certificado en formato X.509
	 * @throws NormalizeCertificateException No se ha podido normalizar el certificado para
	 * 	ser usado con el proveedor criptográfico de Arangi
	 */
	public Certificate(byte[] contenidoCertificado) throws NormalizeCertificateException {
		super();
		
		//-- Obtener el certificado
		ByteArrayInputStream bais = new ByteArrayInputStream (contenidoCertificado);
		X509Certificate certificate = Util.getCertificate(bais);
		
		//-- Cargar el certificado
		this.certificate = normalize (certificate);
		
		//-- Inicializar
		initialize(certificate);
	}
	

	//-- Métodos públicos
	
	/**
	 * Método que devuelve si el certificado es autofirmado
	 * 
	 * @return Cierto si está autofirmado
	 * @throws NormalizeCertificateException Errores en la firma del certificado
	 */
	public boolean isSelfSigned() throws NormalizeCertificateException {
		logger.debug("[Certificate.isSelfSigned]:: Inicio ");

		if (this.selfSigned == null) {
			PublicKey pubKey = certificate.getPublicKey();
			try {
				certificate.verify(pubKey, CRYPTOGRAPHIC_PROVIDER_NAME);
				logger.debug("[Certificate.isSelfSigned]::Es autofirmado ");
				
				this.selfSigned = new Boolean (true);
	
			} catch (InvalidKeyException e) {
				// Is not self signed
				logger.debug("[Certificate.isSelfSigned]::No es autofirmado");
				this.selfSigned = new Boolean (false);
			} catch (CertificateException e) {
				logger.info ("[Certificate.isSelfSigned]::La firma del certificado no está correctamente codificada", e);
				throw new NormalizeCertificateException ("La firma del certificado no está correctamente codificada", e);
			} catch (NoSuchAlgorithmException e) {
				logger.info ("[Certificate.isSelfSigned]::El algoritmo de la firma del certificado no está soportado", e);
				throw new NormalizeCertificateException ("El algoritmo de la firma del certificado no está soportado", e);
			} catch (NoSuchProviderException e) {
				//-- No se debería dar
				logger.info ("[Certificate.isSelfSigned]::No existe el proveedor criptográfico de Arangi", e);
				throw new NormalizeCertificateException ("No existe el proveedor criptográfico de Arangi", e);
			} catch (SignatureException e) {
				logger.info ("[Certificate.isSelfSigned]::Errores en la firma del certificado", e);
				throw new NormalizeCertificateException ("Errores en la firma del certificado", e);
			} 
		}
		
		return this.selfSigned.booleanValue();
	}

	/**
	 * Método que obtiene el OID de la política del certificado. 
	 * 
	 * @return OID de la política del certificado o nulo si no existe la
	 * 	extensión CertificatePolicies
	 */
	public String getPolicyOID() {
		
		logger.debug("[Certificate.getPolicy]::Entrada");

		//-- Accedemos a la extensión del certificatePolicies.
		byte[] extension = certificate.getExtensionValue(X509Extensions.CertificatePolicies.getId());
		
		if (extension == null) {
			return null;
		}
		//-- Obtenemos la política
		try {
			ByteArrayInputStream bais = new ByteArrayInputStream(extension);
			ASN1InputStream asn1IS = new ASN1InputStream(bais);
			
			bais = new ByteArrayInputStream(((ASN1OctetString) asn1IS.readObject()).getOctets());
			asn1IS = new ASN1InputStream(bais);
			
			Enumeration enumeration = ((ASN1Sequence) asn1IS.readObject()).getObjects();
			enumeration = ((ASN1Sequence) enumeration.nextElement()).getObjects();
			
			String policy = ((DERObjectIdentifier) enumeration.nextElement()).getId();
			logger.debug("[Certificate.getPolicy]::La política del certificado es" + policy);
			return policy;
			
		} catch (Exception e) {
			logger.info("[Certificate.getPolicy]::No es posible obtener la extensión del certificado", e);
			return null;
		}
	}
	
	/**
	 * Método que obtiene el Issuer Key Identifier (IKI) del certificado
	 * 
	 * @return Issuer Key Identifier o nulo si éste no se pudo obtener del certificado
	 */
	public String getIssuerKeyIdentifier()  {
		
		byte[] extension = certificate.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
		if (extension == null) {
			return null;
		}
		AuthorityKeyIdentifierStructure akis;
		try {
			akis = new AuthorityKeyIdentifierStructure(extension);
		} catch (Exception e) {
			logger.debug("[Certificate.getSubjectKeyIdentifier]::No es posible obtener el SKI del certificado: " + getCommonName(), e);
			return null;
		}
		return Util.encodeBase64(akis.getKeyIdentifier());
	}

	/**
	 * Método que obtiene el Subject Key Identifier (SKI) a partir del certificado
	 * 
	 * @return Subject Key Identifier o nulo si éste no se pudo obtener del certificado
	 */
	public String getSubjectKeyIdentifier() { 
		
		byte[] extension = certificate.getExtensionValue(X509Extensions.SubjectKeyIdentifier.getId());
		SubjectKeyIdentifierStructure skis;
		try {
			skis = new SubjectKeyIdentifierStructure(extension);
		} catch (Exception e) {
			logger.debug("[Certificate.getSubjectKeyIdentifier]::No es posible obtener el SKI del certificado: " + getCommonName(), e);
			return null;
		}
		return Util.encodeBase64(skis.getKeyIdentifier());
	}
	
	/**
	 * El método obtiene las URLs de las CRLs indicadas en el certificado. Para ello consulta
	 * la extensión CRL Distribution Points (CDP). Si dicha extensión no existe se devolverá
	 * un array sin ningún elemento. 
	 * 
	 * @return URLs de las CRLs del certificado
	 * @throws CertificateFieldException Existe la extensión CDP pero no se puede leer 
	 */
	public String [] getCrlUrls() throws CertificateFieldException{
		
		logger.debug("[Certificate.getCrlUrls]::Entrada");
		
		DERIA5String lDERObjCRL_URL;
		byte[] extBytes = certificate.getExtensionValue(X509Extensions.CRLDistributionPoints.getId());
		
		// En caso de no tener contenido, devolvemos un array vacío.
		if (extBytes == null) {
			logger.debug("[Certificate.getCrlUrls]::No existe la extensión CDP en el certificado");
			return new String[0];
		}
		
		// Load ASN1 object
		ASN1InputStream ais = new ASN1InputStream(new ByteArrayInputStream(extBytes));
		
		try {
			DEROctetString oct = (DEROctetString) ais.readObject();
			ais = new ASN1InputStream(new ByteArrayInputStream(oct.getOctets()));
			CRLDistPoint cdp = new CRLDistPoint((ASN1Sequence) ais.readObject());
			// [BC-135] - point.getDistributionPoints();
			DistributionPoint[] points = cdp.getDistributionPoints();
			
			ArrayList alURLs = new ArrayList();
			for (int i = 0; i < points.length; i++) {
				DistributionPoint point = points[i];
				// [BC-135] - point.getDistributionPoint();
				DistributionPointName name = point.getDistributionPoint();
				// [BC-135] - getName() y getNames()
				GeneralName[] gns = ((GeneralNames) name.getName()).getNames();
				for (int j = 0; j < gns.length; j++) {
					GeneralName gn = gns[j];
					// Si lo que nos llega es una URL, se manda la URL extraida
					// [BC-135] - GeneralName.uniformResourceIdentifier
					if (gn.getTagNo() == GeneralName.uniformResourceIdentifier) {
						lDERObjCRL_URL = DERIA5String.getInstance(gn.getDERObject());
						alURLs.add (lDERObjCRL_URL.getString());
					} else if (gn.getTagNo() == GeneralName.directoryName) {
					}
				}
			}
			
			//-- Return list of CRL URLs
			return (String []) alURLs.toArray(new String [0]);
		
		} catch (IOException e) {
			logger.info("[Certificate.getCrlUrls]::La extensión CDP en el certificado existe pero no se puede leer", e);
			throw new CertificateFieldException ("La extensión CDP en el certificado existe pero no se puede leer", e);
		}
	}
	
	/**
	 * El método obtiene las URLs de los OCSPs que se indican en el certificado. Para ello consulta
	 * la extensión Authority Info Access (AIA). Si dicha extensión no existe se devolverá
	 * un array sin ningún elemento. 
	 * 
	 * @return URLs de los OCSPs del certificado
	 * @throws CertificateFieldException Existe la extensión AIA pero no se puede leer 
	 */
	public String [] getOcspUrls() throws CertificateFieldException{
		
		DERIA5String lDERObjCRL_URL;
		byte[] extBytes = certificate.getExtensionValue(X509Extensions.AuthorityInfoAccess.getId());
		
		// En caso de no tener contenido, devolvemos una cadena vacia. Deberíamos lanzar una excepcion.
		if (extBytes == null) {
			logger.debug("[Certificate.getCrlUrls]::No existe la extensión AIA en el certificado");
			return new String[0];
		}
		
		// Load ASN1 object
		ASN1InputStream ais = new ASN1InputStream(new ByteArrayInputStream(extBytes));
		
		try {
			DEROctetString oct = (DEROctetString) ais.readObject();
			ais = new ASN1InputStream(new ByteArrayInputStream(oct.getOctets()));
			AuthorityInformationAccess aia = new AuthorityInformationAccess ((ASN1Sequence) ais.readObject());
			ArrayList urls = new ArrayList();
			for (int i=0; i<aia.getAccessDescriptions().length; i++) {
				AccessDescription accessDescription = aia.getAccessDescriptions() [i];
				if (accessDescription.getAccessLocation().getTagNo() == GeneralName.uniformResourceIdentifier &&
						accessDescription.getAccessMethod().getId().equals(OID_OCSP_AIA_EXTENSION)) {
					lDERObjCRL_URL = DERIA5String.getInstance(accessDescription.getAccessLocation().getDERObject());
					urls.add(lDERObjCRL_URL.getString());
				}
			}

			return (String[]) urls.toArray(new String[0]);
			
		} catch (IOException e) {
			logger.info("[Certificate.getCrlUrls]::La extensión AIA en el certificado existe pero no se puede leer", e);
			throw new CertificateFieldException ("La extensión AIA en el certificado existe pero no se puede leer", e);
		}
	}
	
	/**
	 * Método que comprueba la validez del certificado respecto a la fecha y hora
	 * actual del sistema. 
	 * 
	 * @return cierto si el fichero está dentro de su intervalo de validez
	 */
	public boolean isActive() {
		
		logger.debug ("[Certificate.isCertificateInValidityInterval]::Entrada");
		
		try {
			certificate.checkValidity();
		} catch (CertificateNotYetValidException e) {
			logger.debug ("[Certificate.isCertificateInValidityInterval]::El certificado aun no es válido");
			return false;
		} catch (CertificateExpiredException e) {
			logger.debug ("[Certificate.isCertificateInValidityInterval]::El certificado ya no es válido");
			return false;
	    }

	    return true;
	}
	
	/**
	 * Método que comprueba si el certificado aun no ha llegado a su periodo de
	 * validez.
	 * 
	 * @return cierto si el fichero aún no ha llegado a su periodo de validez
	 */
	public boolean isNotYetActive() {
		
		logger.debug ("[Certificate.isNotYetActive]::Entrada");
		
		try {
			certificate.checkValidity();
		} catch (CertificateNotYetValidException e) {
			logger.debug ("[Certificate.isNotYetActive]::El certificado aun no es válido");
			return true;
		} catch (CertificateExpiredException e) {
			logger.debug ("[Certificate.isNotYetActive]::El certificado ya no es válido");
			return false;
	    }

	    return false;
	}
	
	/**
	 * Método que comprueba si el certificado ya está caducado: ha sobrepasado su periodo
	 * de validez
	 * 
	 * @return cierto si el fichero está caducado
	 */
	public boolean isExpired() {
		
		logger.debug ("[Certificate.isExpired]::Entrada");
		
		try {
			certificate.checkValidity();
		} catch (CertificateNotYetValidException e) {
			logger.debug ("[Certificate.isExpired]::El certificado aun no es válido");
			return false;
		} catch (CertificateExpiredException e) {
			logger.debug ("[Certificate.isExpired]::El certificado ya no es válido");
			return true;
	    }

	    return false;
	}
	
	/**
	 * Obtiene la fecha de comienzo del periodo de validez del certificado
	 * 
	 * @return Fecha de comienzo del periodo de validez del certificado
	 */
	public Date getValidityPeriodBeginning () {
		
		logger.debug ("[Certificate.getValidityPeriodBeginning]::Entrada");
		
		return certificate.getNotBefore();
	}
	
	/**
	 * Obtiene la fecha de finalización del periodo de validez del certificado
	 * 
	 * @return Fecha de finalización del periodo de validez del certificado
	 */
	public Date getValidityPeriodEnd () {
		
		logger.debug ("[Certificate.getValidityPeriodEnd]::Entrada");
		
		return certificate.getNotAfter();
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave la firma digital
	 * 
	 * @return Cierto si el certificado tiene como uso de clave la firma digital
	 */
	public boolean isKeyUsageDigitalSignature () {
		
		logger.debug ("[Certificate.isKeyUsageDigitalSignature]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[0];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave el no repudio
	 * 
	 * @return Cierto si el certificado tiene como uso de clave el no repudio
	 */
	public boolean isKeyUsageNonRepudiation () {
		
		logger.debug ("[Certificate.isKeyUsageNonRepudiation]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[1];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave el cifrado de claves
	 * 
	 * @return Cierto si el certificado tiene como uso de clave el cifrado de claves
	 */
	public boolean isKeyUsageKeyEncipherment () {
		
		logger.debug ("[Certificate.isKeyUsageKeyEncipherment]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[2];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave el cifrado de datos
	 * 
	 * @return Cierto si el certificado tiene como uso de clave el cifrado de datos
	 */
	public boolean isKeyUsageDataEncipherment () {
		
		logger.debug ("[Certificate.isKeyUsageDataEncipherment]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[3];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave el acuerdo de claves
	 * 
	 * @return Cierto si el certificado tiene como uso de clave el acuerdo de claves
	 */
	public boolean isKeyUsageKeyAgreement () {
		
		logger.debug ("[Certificate.isKeyUsageKeyAgreement]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[4];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave la firma de certificados
	 * 
	 * @return Cierto si el certificado tiene como uso de clave la firma de certificados
	 */
	public boolean isKeyUsageKeyCertSign () {
		
		logger.debug ("[Certificate.isKeyUsageKeyCertSign]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[5];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave la firma de CRLs
	 * 
	 * @return Cierto si el certificado tiene como uso de clave la firma de CRLs
	 */
	public boolean isKeyUsageCRLSign () {
		
		logger.debug ("[Certificate.isKeyUsageCRLSign]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[6];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave sólo el cifrado
	 * 
	 * @return Cierto si el certificado tiene como uso de clave sólo el cifrado
	 */
	public boolean isKeyUsageEncipherOnly () {
		
		logger.debug ("[Certificate.isKeyUsageEncipherOnly]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[7];
	}
	
	/**
	 * Comprueba si el certificado tiene como uso de clave sólo el descifrado
	 * 
	 * @return Cierto si el certificado tiene como uso de clave sólo el descifrado
	 */
	public boolean isKeyUsageDecipherOnly () {
		
		logger.debug ("[Certificate.isKeyUsageDecipherOnly]::Entrada");
		
		if (certificate.getKeyUsage() == null) {
			return true;
		}
		
		return certificate.getKeyUsage()[8];
	}
	
	/**
	 * Obtiene la lista de usos de clave extendidos. Las constantes con los tipos
	 * definidos en el estándar se encuentran en org.bouncycastle.asn1.x509.KeyPurposeId.
	 * Por ejemplo, si queremos saber si el certificado tiene la extensión de firma de
	 * OCSP:
	 * <code>
	 * 	if (certificate.getExtendedKeyUsage().contains (KeyPurposeId.id_kp_OCSPSigning.getId())) { ... }
	 * </code>
	 * 
	 * @return Lista de usos de clave extendidos
	 * @throws CertificateException La extensión no puede ser descodificada
	 */
	public List<String> getExtendedKeyUsage () throws es.accv.arangi.base.exception.CertificateException {
		
		logger.debug ("[Certificate.getExtendedKeyUsage]::Entrada");
		
		try {
			List<String> result = certificate.getExtendedKeyUsage();
			if (result == null) {
				return new ArrayList<String>();
			}
			return result;
		} catch (CertificateParsingException e) {
			logger.info("[Certificate.getExtendedKeyUsage]::La extensión no puede ser descodificada", e);
			throw new es.accv.arangi.base.exception.CertificateException("La extensión no puede ser descodificada", e);
		} 
	}
	
	/**
	 * Método que devuelve la huella Digital (FingerPrint) del certificado. Es
	 * el mismo resultado de llamar a {@link #getDigest(java.lang.String) getDigest} más
	 * una transformación del resultado a hexadecimal.<br><br>
	 * 
	 * El FingerPrint ha sido creado utilizando como algoritmo de hashing "SHA1".
	 * 
	 * @return String que contienen la huella digital del certificado.
	 */
	public String getFingerPrint() {
		return getFingerPrint(DEFAULT_HASHING_ALGORITHM);
	}
	
	/**
	 * Método que devuelve la huella Digital (FingerPrint) del certificado. Es
	 * el mismo resultado de llamar a {@link #getDigest(java.lang.String) getDigest} más
	 * una transformación del resultado a hexadecimal.
	 * 
	 * @param hashingAlgorithm Algoritmo de hash a utilizar
	 * @return String que contienen la huella digital del certificado.
	 */
	public String getFingerPrint(String hashingAlgorithm) {
		
		logger.debug ("[Certificate.getFingerPrint]::Entrada");

	    try {
	    	// Obtenemos el hash del certificado
	    	byte[] lBytesHash = getDigest(hashingAlgorithm);
	    	
	    	// Convertimos a Hexadecimal...
	    	byte[] lBytesHash_HEX = Hex.encode(lBytesHash);
	    	
	    	// Obtenemos la representación en String del Hash en Hexadecimal...
	    	String lStrHash = new String(lBytesHash_HEX);
	    	
	    	return lStrHash.toUpperCase();
	    } catch (CertificateEncodingException e0) {
	    	logger.info ("[Certificate.getFingerPrint]::Error de codificación del certificado");
	    	return "";
	    } catch (NoSuchAlgorithmException e2) {
	    	logger.info ("[Certificate.getFingerPrint]::No se encuentra el algoritmo " + DEFAULT_HASHING_ALGORITHM);
	    	return "";
	    }
	}
	
	/**
	 * Método que devuelve el digest del certificado.<br><br>
	 * 
	 * El digest ha sido creado utilizando con el algoritmo de hashing "SHA1".
	 * 
	 * @return String que contienen el digest del certificado.
	 * @throws NoSuchAlgorithmException No existe el algoritmo de hashing para
	 * 	el proveedor criptográfico de Arangí
	 * @throws CertificateEncodingException Error obteniendo la codificación
	 * 	DER del documento
	 */
	public byte[] getDigest() throws CertificateEncodingException, NoSuchAlgorithmException {
		return getDigest(DEFAULT_HASHING_ALGORITHM);
	}
	
	/**
	 * Método que devuelve el digest del certificado.
	 * 
	 * @param hashingAlgorithm Algoritmo de hash a utilizar
	 * @return String que contienen el digest del certificado.
	 * @throws NoSuchAlgorithmException No existe el algoritmo de hashing para
	 * 	el proveedor criptográfico de Arangí
	 * @throws CertificateEncodingException Error obteniendo la codificación
	 * 	DER del documento
	 */
	public byte[] getDigest(String hashingAlgorithm) throws NoSuchAlgorithmException, CertificateEncodingException {
		
		logger.debug ("[Certificate.getDigest]::Entrada::" + hashingAlgorithm);

    	// Obtenemos un objeto para crear "hash" de datos utilizando una
    	MessageDigest lObjHashMaker = MessageDigest.getInstance(hashingAlgorithm);
    	
    	// Obtenemos el hash del certificado
    	return lObjHashMaker.digest(certificate.getEncoded());
	    	
	}
	
	/**
	 * Obtiene elementos del Asunto (Subject) del certificado en base a su OID.<br><br>
	 * 
	 * Por ejemplo, para obtener el campo GIVENNAME (G) del Asunto (Subject):<br>
	 * <code>
	 * 	certificate.getElementSubject(X509Name.GIVENNAME)
	 * </code>
	 * 
	 * @param oid OID dele elemento buscado
	 * @return Valor del elemento
	 */
	public String getElementSubject (DERObjectIdentifier oid) {
		
		logger.debug ("[Certificate.getElementSubject]::Buscando OID '" + oid.getId() + "' en el subject");

		X509Name subjectName = this.asn1Certificate.getSubject();
		Vector v = subjectName.getValues(oid);
		if (v == null || v.isEmpty()) {
			logger.debug ("[Certificate.getElementSubject]::Para el OID '" + oid.getId() + "' no hay ningún elemento en el subject");
			return null;
		}
		return (String) v.get(0);
	}

	/**
	 * Obtiene el common name que se encuentra en el asunto del certificado
	 * 
	 * @return Common Name
	 */
	public String getCommonName () {
		
		logger.debug ("[Certificate.getCommonName]::Entrada");
		
		return getElementSubject(X509Name.CN);
	}
	
	/**
	 * Obtiene el país que se encuentra en el asunto del certificado
	 * 
	 * @return País
	 */
	public String getCountry () {
		
		logger.debug ("[Certificate.getCountry]::Entrada");
		
		return getElementSubject(X509Name.C);
	}
	
	/**
	 * Obtiene la cadena de texto contenida en el Subject del certificado
	 * 
	 * @return Name del Distinguished Name que forma el Subject del certificado
	 */
	public String getSubjectDN () {
		return this.certificate.getSubjectDN().getName();
	}
	
	/**
	 * Obtiene la cadena de texto contenida en el Issuer del certificado
	 * 
	 * @return Name del Distinguished Name que forma el Issuer del certificado
	 */
	public String getIssuerDN () {
		return this.certificate.getIssuerDN().getName();
	}
	
	/**
	 * Obtiene el common name del Issuer del certificado
	 * 
	 * @return Common name del Issuer del certificado
	 */
	public String getIssuerCommonName () {
		
		logger.debug ("[Certificate.getIssuerCommonName]::Entrada");
		
		String dn = this.certificate.getIssuerDN().getName();
		StringTokenizer st = new StringTokenizer(dn, ",");
		while(st.hasMoreTokens()) {
			String token = st.nextToken();
			if (token.startsWith("CN=")) {
				return token.substring(3);
			}
		}
		
		return null;
	}
	
	/**
	 * Obtiene una lista que representa el contenido de la extensión Subject Alternative 
	 * Name (Nombre Alternativo del Sujeto). Cada elemento es de tipo 
	 * {@link es.accv.arangi.base.util.AlternativeNameElement AlternativeNameElement} 
	 * 
	 * @return Representación del Subject Alternative Name
	 * @throws CertificateFieldException Error leyendo el certificado
	 */
	public List getSubjectAlternativeName() throws CertificateFieldException {
		
		logger.debug ("[Certificate.getSubjectAlternativeNames]::Entrada");
		
		byte[] extVal = certificate.getExtensionValue(X509Extensions.SubjectAlternativeName.getId());

		try {
			return getAlternativeNames(extVal);
		} catch (CertificateParsingException e) {
			logger.info("[Certificate.getSubjectAlternativeNames]::Error leyendo la extensión SubjectAlternativeName", e);
			throw new CertificateFieldException ("Error leyendo la extensión SubjectAlternativeName", e);
		}
	}

	/**
	 * Busca un elemento dentro de la extensión Subject Alternative Name. Normalmente
	 * el vector sólo contendrá el elemento que estamos buscando.<br><br>
	 * Por ejemplo:<br>
	 * <code>
	 * 	(String) certificate.getSubjectAlternativeNameElement("0.9.2342.19200300.100.1.1").get(0)
	 * </code>
	 * 
	 * @param oid OID del elemento a Buscar
	 * @return Vector con la información del elemento
	 * @throws CertificateFieldException Error leyendo el certificado
	 */
	public Vector getSubjectAlternativeNameElement (String oid) throws CertificateFieldException {
		
		logger.debug ("[Certificate.getSubjectAlternativeNameElement]::Entrada::" + oid);
		
		//-- Obtener la lista
		List lNames = getSubjectAlternativeName();
		
		//-- Iterar
		for (Iterator iterator = lNames.iterator(); iterator.hasNext();) {
			AlternativeNameElement element = (AlternativeNameElement) iterator.next();
			if (element.getValue() instanceof Map) {
				Map mapElement = (Map) element.getValue();
				if (mapElement.containsKey(oid)) {
					logger.debug ("[Certificate.getSubjectAlternativeNameElement]::Encontrado el elemento::" + (Vector) mapElement.get(oid));
					return (Vector) mapElement.get(oid);
				}
			}
		}
		
		logger.debug ("[Certificate.getSubjectAlternativeNameElement]::No se ha encontrado el elemento para el oid " + oid);
		return null;
	}
	
	/**
	 * Obtiene una lista que representa el contenido de la extensión Issuer Alternative 
	 * Name (Nombre Alternativo del Emisor). Cada elemento es de tipo 
	 * {@link es.accv.arangi.base.util.AlternativeNameElement AlternativeNameElement} 
	 * 
	 * @return Representación del Issuer Alternative Name
	 * @throws CertificateFieldException Error leyendo el certificado
	 */
	public List getIssuerAlternativeName() throws CertificateFieldException {
		
		logger.debug ("[Certificate.getIssuerAlternativeNames]::Entrada");
		
		byte[] extVal = certificate.getExtensionValue(X509Extensions.IssuerAlternativeName.getId());

		try {
			return getAlternativeNames(extVal);
		} catch (CertificateParsingException e) {
			logger.info("[Certificate.getIssuerAlternativeNames]::Error leyendo la extensión IssuerAlternativeName", e);
			throw new CertificateFieldException ("Error leyendo la extensión IssuerAlternativeName", e);
		}
	}

	/**
	 * Busca un elemento dentro de la extensión Issuer Alternative Name 
 	 * 
 	 * @param oid OID del elemento a Buscar
 	 * @return Vector con la información del elemento
 	 * @throws CertificateFieldException Error leyendo el certificado
 	 */
 	public Vector getIssuerAlternativeNameElement (String oid) throws CertificateFieldException {
 		
 		logger.debug ("[Certificate.getIssuerAlternativeNameElement]::Entrada::" + oid);
 		
 		//-- Obtener la lista
 		List lNames = getIssuerAlternativeName();
 		
 		//-- Iterar
 		for (Iterator iterator = lNames.iterator(); iterator.hasNext();) {
 			AlternativeNameElement element = (AlternativeNameElement) iterator.next();
 			if (element.getValue() instanceof Map) {
 				Map mapElement = (Map) element.getValue();
 				if (mapElement.containsKey(oid)) {
 					logger.debug ("[Certificate.getIssuerAlternativeNameElement]::Encontrado el elemento::" + (Vector) mapElement.get(oid));
 					return (Vector) mapElement.get(oid);
 				}
 			}
 		}
 		
 		logger.debug ("[Certificate.getIssuerAlternativeNameElement]::No se ha encontrado el elemento para el oid " + oid);
 		return null;
 	}
 	
 	/**
 	 * Método que devuelve el Número de Serie del certificado (Hexadecimal)
 	 * 
 	 * @return Número de Serie del certificado.
 	 * 
 	 */
 	public String getSerialNumber() {
 
 		logger.debug ("[Certificate.getSerialNumber]::Entrada");
 		
 		return certificate.getSerialNumber().toString(16).toUpperCase();
 	}
 	
 	/**
 	 * Método que devuelve el Número de Serie del certificado (BigInteger)
 	 * 
 	 * @return Número de Serie del certificado.
 	 * 
 	 */
 	public BigInteger getSerialNumberBigInteger() {
 
 		logger.debug ("[Certificate.getSerialNumberBigInteger]::Entrada");
 		
 		return certificate.getSerialNumber();
 	}
 	
 	/**
 	 * Obtiene el issuer and serial number. En algunos casos se utiliza este
 	 * valor para identificar el certificado en lugar del subject key
 	 * identifier.
 	 * 
 	 * @return Issuer and serial number
 	 * @throws CertificateFieldException No se ha podido leer correctamente la 
 	 * 	información del certificado
 	 */
 	public IssuerAndSerialNumber getIssuerAndSerialNumber() throws CertificateFieldException {
 		
 		logger.debug ("[Certificate.getIssuerAndSerialNumber]::Entrada");
 		
 		try {
 	        ASN1InputStream inputStreamTbsCertificate = new ASN1InputStream(new ByteArrayInputStream(toX509Certificate().getTBSCertificate()));
 	        ASN1Sequence seqAux = (ASN1Sequence)inputStreamTbsCertificate.readObject();
 	        DERObject tbsCertificateDER = (DERObject)seqAux.getObjectAt(seqAux.getObjectAt(0) instanceof DERTaggedObject ? 3 : 2);
 			return new IssuerAndSerialNumber(new X509Name((ASN1Sequence)tbsCertificateDER),
 			   		new DERInteger(toX509Certificate().getSerialNumber()));
 		} catch (CertificateEncodingException e) {
 			logger.info("[Certificate.toDER]::Error obteniendo la información del certificado en formato DER", e);
 			throw new CertificateFieldException ("Error obteniendo la información del certificado en formato DER", e);
 		} catch (IOException e) {
 			logger.info("[Certificate.toDER]::Error leyendo la información del certificado en formato DER", e);
 			throw new CertificateFieldException ("Error leyendo la información del certificado en formato DER", e);
 		} 
 	}
 
 	/**
 	 * Obtiene la clave pública asociada al certificado
 	 * 
 	 * @return Clave pública del certificado
 	 */
 	public PublicKey getPublicKey () {
 		
 		logger.debug ("[Certificate.getPublicKey]::Entrada");
 		
 		return certificate.getPublicKey();
 	}
 	
 	/**
 	 * Devuelve el certificado en formato X509Certificate
 	 * 
 	 * @return X509 Certificate
 	 */
 	public X509Certificate toX509Certificate () {
 		return certificate;
 	}
 	
 	/**
 	 * Devuelve el certificado en formato DER
 	 * 
 	 * @return Certificado en formato DER
 	 * @throws NormalizeCertificateException No se ha podido codificar el certificado en 
 	 * 	formato DER
 	 */
 	public byte[] toDER () throws NormalizeCertificateException {
 		try {
 			return certificate.getEncoded();
 		} catch (CertificateEncodingException e) {
 			logger.info("[Certificate.toDER]::Error codificando el certificado en formato DER", e);
 			throw new NormalizeCertificateException ("Error codificando el certificado en formato DER", e);
 		}
 	}
 	
 	/**
 	 * Devuelve el certificado en formato PEM
 	 * 
 	 * @return Certificado en formato PEM
 	 * @throws NormalizeCertificateException No se ha podido codificar el certificado en 
 	 * 	formato DER antes de pasarlo a PEM
 	 */
 	public String toPEM () throws NormalizeCertificateException {
 		
 		return "-----BEGIN CERTIFICATE-----\n" + Util.encodeBase64(toDER()) + 
 			"\n-----END CERTIFICATE-----";
 	}
 	
 	/**
 	 * Crea un fichero PKCS#7 en el que almacena el certificado y su cadena de confianza.
 	 * 
 	 * @param chain Cadena de confianza. Si se pasa un valor nulo sólo se almacenará el
 	 * 	certificado.
 	 * @return Fichero PKCS#7 en formato DER
 	 * @throws SignatureException Errores creando el almacén PKCS#7
 	 */
     public byte[] toPKCS7(Certificate[] chain) throws SignatureException {
 
     	logger.debug("[Certificate.toPKCS7]::Entrada::" + (chain==null?"null":chain.length));
     	
     	//-- Objeto PKCS#7 
     	CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
     	
     	//-- Construir lista de certificados
     	X509Certificate certificate = toX509Certificate();
     	List certList = new ArrayList();
     	certList.add(certificate);
     	if (chain != null) {
 	    	for (int i = 0; i < chain.length; i++) {
 				certList.add(chain[i].toX509Certificate());
 			}
     	}
     	
     	//-- Unirlo todo y devolver en formato DER
     	try {
     		CertStore store = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList));
     		gen.addCertificatesAndCRLs(store);
     		CMSProcessableByteArray process = new CMSProcessableByteArray(null); //no hay firmas
     		CMSSignedData data = gen.generate(process, CRYPTOGRAPHIC_PROVIDER_NAME);
     		ContentInfo contentInfo = data.getContentInfo();
     		return contentInfo.getDEREncoded();
     	} catch (InvalidAlgorithmParameterException e) {
 			logger.info ("[Certificate.toPKCS7]::El algoritmo de la firma del certificado no es válido", e);
 			throw new SignatureException ("El algoritmo de la firma del certificado no es válido", e);
 		} catch (NoSuchAlgorithmException e) {
 			logger.info ("[Certificate.toPKCS7]::El algoritmo de la firma del certificado no está soportado", e);
 			throw new SignatureException ("El algoritmo de la firma del certificado no está soportado", e);
 		} catch (CertStoreException e) {
 			logger.info ("[Certificate.toPKCS7]::El certificado no puede ser almacenado", e);
 			throw new SignatureException ("El certificado no puede ser almacenado", e);
 		} catch (CMSException e) {
 			logger.info ("[Certificate.toPKCS7]::El certificado no puede ser almacenado en formato PKCS#7", e);
 			throw new SignatureException ("El certificado no puede ser almacenado en formato PKCS#7", e);
 		} catch (NoSuchProviderException e) {
 			//-- No se debería dar
 			logger.info ("[Certificate.toPKCS7]::No existe el proveedor criptográfico de Arangí", e);
 			throw new SignatureException ("No existe el proveedor criptográfico de Arangí", e);
 //		} catch (IOException e) {
 //			logger.info ("[Certificate.toPKCS7]::Excepción de entrada/salida", e);
 //			throw new SignatureCertificateException ("Excepción de entrada/salida", e);
 		} 
 
     }    
     
 	/**
 	 * Guarda el certificado en formato DER en el fichero que se pasa como parámetro
 	 * 
 	 * @param file Fichero donde se guardará el certificado
 	 * @throws Exception No se ha podido guardar el fichero
 	 * @throws NormalizeCertificateException No se ha podido codificar el certificado en 
 	 * 	formato DER
 	 */
 	public void save (File file) throws NormalizeCertificateException, Exception {
 		Util.saveFile(file, toDER());
 	}
 	
 	/**
 	 * Guarda el certificado en formato PEM en el fichero que se pasa como parámetro
 	 * 
 	 * @param file Fichero donde se guardará el certificado
 	 * @throws Exception No se ha podido guardar el fichero
 	 * @throws NormalizeCertificateException No se ha podido codificar el certificado en 
 	 * 	formato DER antes de pasarlo a PEM
 	 */
 	public void saveToPEM (File file) throws NormalizeCertificateException, Exception {
 		Util.saveFile(file, toPEM().getBytes());
 	}
 	
 	/**
 	 * Devuelve en texto la información más importante del certificado.
 	 * 
 	 * @return Información del certificado
 	 */
 	public String toString () {
 		return certificate.toString();
 	}
 	
 	//-- Métodos estáticos
 	
 	/**
 	 * Normaliza los certificados de acuerdo al proveedor criptográfico de Arangi.
 	 * 
 	 * @param originalCert Certificado a normalizar
 	 * @return X509Certificate Certificado normalizado de acuerdo al proveedor criptográfico 
 	 * de Arangi
 	 * 
 	 * @throws Exception Error durante la normalización
 	 */
 	public static X509Certificate normalize (X509Certificate originalCert) throws NormalizeCertificateException {
 		
 		byte[] bytes;
 		try {
 			bytes = originalCert.getEncoded();
 		} catch (CertificateEncodingException e) {
 			logger.info ("[Certificate.normalize]::Error de codificación del certificado", e);
 			throw new NormalizeCertificateException ("Error de codificación del certificado", e);
 		}
 		
 		ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
 		CertificateFactory cf;
 		try {
 			cf = CertificateFactory.getInstance("X.509", CRYPTOGRAPHIC_PROVIDER);
 		} catch (CertificateException e) {
 			logger.info ("[Certificate.normalize]::El tipo del certificado no está disponible para el proveedor criptográfico de Arangi: " + CRYPTOGRAPHIC_PROVIDER, e);
 			throw new NormalizeCertificateException ("El tipo del certificado no está disponible para el proveedor criptográfico de Arangi: " + CRYPTOGRAPHIC_PROVIDER, e);
 		} 
 		
 		X509Certificate normalizedCertificate;
 		try {
 			normalizedCertificate = (X509Certificate) cf.generateCertificate(bais);
 		} catch (CertificateException e) {
 			logger.info ("[Certificate.normalize]::Error generando el certificado", e);
 			throw new NormalizeCertificateException ("Error generando el certificado", e);
 		}
 		try {
 			bais.close();
 		} catch (IOException e) {}
 		
 		return normalizedCertificate;
 		
 	}
 	
 	/**
 	 * Método estático que obtiene el Issuer Key Identifier (IKI) a partir de un certificado
 	 * X.509.
 	 * 
 	 * @param certificate Certificado X.509
 	 * @return Issuer Key Identifier o nulo si éste no se pudo obtener del certificado
 	 */
 	public static String getIssuerKeyIdentifier(X509Certificate certificate)  {
 		
 		byte[] extension = certificate.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
 		if (extension == null) {
 			return null;
 		}
 		AuthorityKeyIdentifierStructure akis;
 		try {
 			akis = new AuthorityKeyIdentifierStructure(extension);
 		} catch (IOException e) {
 			return null;
 		}
 		return Util.encodeBase64(akis.getKeyIdentifier());
 	}
 
 	/**
 	 * Método estático que obtiene el Subject Key Identifier (SKI) a partir de un certificado
 	 * X.509.
 	 * 
 	 * @param certificate X.509 Certificate
 	 * @return Subject Key Identifier o nulo si éste no se pudo obtener del certificado
 	 */
 	public static String getSubjectKeyIdentifier(X509Certificate certificate) { 
 		
 		byte[] extension = certificate.getExtensionValue(X509Extensions.SubjectKeyIdentifier.getId());
 		SubjectKeyIdentifierStructure skis;
 		try {
 			skis = new SubjectKeyIdentifierStructure(extension);
 		} catch (IOException e) {
 			return null;
 		}
 		return Util.encodeBase64(skis.getKeyIdentifier());
 	}
 	
     /**
      * Crea un objeto (Bouncycastle) X509Name a partir de un String con un DN. OIDs conocidos
      * (en orden) son:
      * <code> EmailAddress, UID, CN, SN (SerialNumber), GivenName, Initials, SurName, T, OU,
      * O, L, ST, DC, C </code>
      * 
      * @param dn String que contiene un DN que puede ser transformado en un X509Name. El String
      *          DN tiene el formato "CN=zz,OU=yy,O=foo,C=SE". OIDs desconocidos en el String
      *          se añadirán al final del array de OIDs
      * 
      * @return X509Name o null si el parámetro es nulo
      */
     public static X509Name stringToBcX509Name(String dn) {
 
       // log.debug(">stringToBcX509Name: " + dn);
       if (dn == null)
         return null;
 
       Vector defaultOrdering = new Vector();
       Vector values = new Vector();
       X509NameTokenizer xt = new X509NameTokenizer(dn);
 
       while (xt.hasMoreTokens()) {
         // This is a pair (CN=xx)
         String pair = xt.nextToken();
         int ix = pair.indexOf("=");
 
         if (ix != -1) {
           String key = pair.substring(0, ix).toLowerCase();
           String val = pair.substring(ix + 1);
 
           // -- First search the OID by name in declared OID's
           DERObjectIdentifier oid = (DERObjectIdentifier) oids.get(key.toLowerCase());
 
           // -- If isn't declared, we try to create it
           if (oid == null) {
             oid = new DERObjectIdentifier(key);
           }
 
           defaultOrdering.add(oid);
           values.add(val);
 
         } else {
           logger.info("[Certificate.stringToBcX509Name]:: Algún par OID=valor no es correcto: " + dn);
         }
       }
 
       X509Name x509Name = new X509Name(defaultOrdering, values);
 
       //-- Reorder fields
       X509Name orderedX509Name = getOrderedX509Name(x509Name, getDefaultX509FieldOrder());
 
       return orderedX509Name;
       
       
     } 
     
     /**
      * Genera un certificado autofirmado con los parámetros que se le pasan. El periodo de
      * validez comienza en el instante actual.
      *
      * @param dn DN del emisor/issuer (es mismo que el del asunto/subject) 
      * @param validity Días de validez
      * @param policyId String con la política o nulo
      * @param privKey Clave privada con la que se firmará el certificado
      * @param pubKey Clave pública
      * @param isCA Si cierto se marcará el certificado como de una autoridad de certificación
      *
      * @return X509Certificate auto firmado
      * @throws NoSuchAlgorithmException No existe el algoritmo SHA-1 Pseudo-Random Number 
      * 	Generation, necesario para obtener el número de serie del certificado
      * @throws SignatureException Error firmando el certificado
      * @throws IllegalStateException 
      * @throws InvalidKeyException La clave privada no es válida
      * @throws CertificateEncodingException Error codificando el nuevo certificado
      */
     public static X509Certificate generateSelfCertificate(String dn, long validity, String policyId,
         PrivateKey privKey, PublicKey pubKey, boolean isCA) throws NoSuchAlgorithmException, CertificateEncodingException, InvalidKeyException, IllegalStateException, SignatureException
     {
     	return generateSelfCertificate(dn, validity, policyId, privKey, pubKey, isCA, ArangiObject.DEFAULT_SIGNING_ALGORITHM);
     }
     
     /**
      * Genera un certificado autofirmado con los parámetros que se le pasan. El periodo de
      * validez comienza en el instante actual.
      *
      * @param dn DN del emisor/issuer (es mismo que el del asunto/subject) 
      * @param validity Días de validez
      * @param policyId String con la política o nulo
      * @param privKey Clave privada con la que se firmará el certificado
      * @param pubKey Clave pública
      * @param isCA Si cierto se marcará el certificado como de una autoridad de certificación
      * @param signatureAlgorithm Algoritmo de la firma del certificado
      *
      * @return X509Certificate auto firmado
      * @throws NoSuchAlgorithmException No existe el algoritmo SHA-1 Pseudo-Random Number 
      * 	Generation, necesario para obtener el número de serie del certificado
      * @throws SignatureException Error firmando el certificado
      * @throws IllegalStateException 
      * @throws InvalidKeyException La clave privada no es válida
      * @throws CertificateEncodingException Error codificando el nuevo certificado
      */
     public static X509Certificate generateSelfCertificate(String dn, long validity, String policyId,
         PrivateKey privKey, PublicKey pubKey, boolean isCA, String signatureAlgorithm) throws NoSuchAlgorithmException, CertificateEncodingException, InvalidKeyException, IllegalStateException, SignatureException
     {
     	
         // Create self signed certificate
         Date firstDate = new Date();
 
         // Set back startdate ten minutes to avoid some problems with wrongly set clocks.
         firstDate.setTime(firstDate.getTime() - (10 * 60 * 1000));
 
         Date lastDate = new Date();
 
         // validity in days = validity*24*60*60*1000 milliseconds
         lastDate.setTime(lastDate.getTime() + (validity * (24 * 60 * 60 * 1000)));
 
         X509V3CertificateGenerator certgen = new X509V3CertificateGenerator();
 
         // Serialnumber is random bits, where random generator is initialized with Date.getTime() when this
         // bean is created.
         byte[] serno = new byte[8];
         SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
         random.setSeed((new Date().getTime()));
         random.nextBytes(serno);
         certgen.setSerialNumber((new java.math.BigInteger(serno)).abs());
         certgen.setNotBefore(firstDate);
         certgen.setNotAfter(lastDate);
         certgen.setSignatureAlgorithm(signatureAlgorithm);
         certgen.setSubjectDN(stringToBcX509Name(dn));
         certgen.setIssuerDN(stringToBcX509Name(dn));
         certgen.setPublicKey(pubKey);
 
         // Basic constranits is always critical and MUST be present at-least in CA-certificates.
         BasicConstraints bc = new BasicConstraints(isCA);
         certgen.addExtension(X509Extensions.BasicConstraints.getId(), true, bc);
 
         // Put critical KeyUsage in CA-certificates
         if (isCA) {
             int keyusage = X509KeyUsage.keyCertSign + X509KeyUsage.cRLSign;
             X509KeyUsage ku = new X509KeyUsage(keyusage);
             certgen.addExtension(X509Extensions.KeyUsage.getId(), true, ku);
         }
 
         // Subject and Authority key identifier is always non-critical and MUST be present for certificates to verify in Mozilla.
         try {
             if (isCA == true) {
                 SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(
                             new ByteArrayInputStream(pubKey.getEncoded())).readObject());
                 SubjectKeyIdentifier ski = new SubjectKeyIdentifier(spki);
 
                 SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(
                             new ByteArrayInputStream(pubKey.getEncoded())).readObject());
                 AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(apki);
 
                 certgen.addExtension(X509Extensions.SubjectKeyIdentifier.getId(), false, ski);
                 certgen.addExtension(X509Extensions.AuthorityKeyIdentifier.getId(), false, aki);
             }
         } catch (IOException e) { // do nothing
         }
 
         // CertificatePolicies extension if supplied policy ID, always non-critical
         if (policyId != null) {
                 PolicyInformation pi = new PolicyInformation(new DERObjectIdentifier(policyId));
                 DERSequence seq = new DERSequence(pi);
                 certgen.addExtension(X509Extensions.CertificatePolicies.getId(), false, seq);
         }
 
         X509Certificate selfcert = certgen.generate(privKey);
 
         return selfcert;
     } 
     
     /**
      * Devolverá cierto si el certificado contiene la extensión "Comprobación 
      * de no revocación". Esta extensión se suele utilizar en certificados de OCSP.
      * 
      * @return Cierto si el certificado contiene la extensión "Comprobación 
      * 	de no revocación". Falso en caso contrario.
      */
     public boolean hasNoRevocationCheck () {
     	logger.debug("[Certificate.hasNoRevocationCheck]::Entrada");
     	boolean result = certificate.getExtensionValue(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck.getId()) != null;
     	logger.debug("[Certificate.hasNoRevocationCheck]::Resultado: " + result);
     	
     	return result;
     }
 
     
     @Override
     public boolean equals(Object obj) {
     	
     	//-- Si no son del mismo tipo no son iguales
     	if (!(obj instanceof Certificate)) {
     		return false;
     	}
     	
     	//-- Son iguales si tienen el mismo issuerAndSerialNumber
     	Certificate certificateToCompare = (Certificate) obj;
     	IssuerAndSerialNumber issuerSN1;
     	IssuerAndSerialNumber issuerSN2;
 		try {
 			issuerSN1 = certificateToCompare.getIssuerAndSerialNumber();
 			issuerSN2 = getIssuerAndSerialNumber();
 		} catch (CertificateFieldException e) {
 			return false;
 		}
     	
     	if (issuerSN1.getName().equals(issuerSN2.getName()) &&
     			issuerSN1.getSerialNumber().getValue().equals(issuerSN2.getSerialNumber().getValue())) {
     		return true;
     	}
     	
     	return false;
     }
     
 	//-- Métodos privados
 	
 	/**
 	 * Método que inicializa la clase
 	 * 
 	 * @throws NormalizeCertificateException No se puede normalizar el certificado
 	 */
 	private void initialize (X509Certificate certificate) throws NormalizeCertificateException {
 		
 		//-- Cargar el certificado
 		this.certificate = normalize (certificate);
 
 		//-- Cargar el certificado en ASN.1
 		try {
 			ASN1InputStream ais = new ASN1InputStream(certificate.getEncoded());
 			DERObject obj = ais.readObject();
 			ASN1Sequence seq = (ASN1Sequence)obj;
 			ais.close();
 			this.asn1Certificate = new X509CertificateStructure (seq);
 		} catch (Exception e) {
 			logger.info("[Certificate.initialize]::No ha sido posible obtener una representación ASN.1 del certificado", e);
 			throw new NormalizeCertificateException ("No ha sido posible obtener una representación ASN.1 del certificado", e);
 		}
 
 	}
 
 	/*
 	 * Reune los valores de la extensión en una lista en la que cada elemento es una lista
 	 */
 	private List getAlternativeNames(byte[] extVal) throws CertificateParsingException {
 		List result = new ArrayList ();
 		if (extVal == null) {
 			return result;
 		}
 		try {
 			byte[] extnValue = DEROctetString.getInstance(ASN1Object.fromByteArray(extVal)).getOctets();
 			Enumeration it = DERSequence.getInstance(ASN1Object.fromByteArray(extnValue)).getObjects();
 			while (it.hasMoreElements()) {
 				GeneralName genName = GeneralName.getInstance(it.nextElement());
 				AlternativeNameElement element = new AlternativeNameElement ();
 				element.setType(genName.getTagNo());
 				String value;
 				switch (genName.getTagNo()) {
 					case GeneralName.ediPartyName:
 					case GeneralName.x400Address:
 					case GeneralName.otherName:
 						element.setValue(genName.getName().getDERObject());
 						break;
 					case GeneralName.directoryName:
 						element.setValue(X509Name.getInstance(genName.getName()));
 						break;
 					case GeneralName.dNSName:
 					case GeneralName.rfc822Name:
 					case GeneralName.uniformResourceIdentifier:
 						element.setValue(((DERString)genName.getName()).getString());
 						break;
 					case GeneralName.registeredID:
 						element.setValue(DERObjectIdentifier.getInstance(genName.getName()).getId());
 						break;
 					case GeneralName.iPAddress:
 						element.setValue(new String (DEROctetString.getInstance(genName.getName()).getOctets()));
 						break;
 					default:
 						throw new IOException("Bad tag number: " + genName.getTagNo());
 				}
 
 				result.add(element);
 			}
 		} catch (Exception e) {
 			throw new CertificateParsingException(e.getMessage());
 		}
 		return result;
 	}
 
     /*
      * Obtiene un vector con el DERObjectIdentifiers para los DNObjects names
      * 
      * @return
      */
     private static Vector getDefaultX509FieldOrder(){
       
       Vector fieldOrder = new Vector();
       
       for (int i = 0; i < dNObjects.length; i++) {
         fieldOrder.add((DERObjectIdentifier) oids.get(dNObjects[i].toLowerCase()));
       }
       
       return fieldOrder;
       
     }
     
     /**
      * Obtiene un X509Name reordenado, si algunos campos del X509Name original no
      * aparecen en el parámetro 'ordering' se añadirán al final
      *   
      * @param x509Name
      * @param ordering, Vector de DERObjectIdentifier
      * @return
      */
     private static X509Name getOrderedX509Name( X509Name x509Name, Vector ordering ){
       
       //-- Null prevent
       if ( ordering == null ){ ordering = new Vector(); }
       
       //-- New order for the X509 Fields
       Vector newOrdering  = new Vector();
       Vector newValues    = new Vector();
       
       HashMap ht = new HashMap();
       Iterator it = ordering.iterator();
       
       logger.debug("Add ordered fields: ");
       //-- Add ordered fields
       while( it.hasNext() ){
         DERObjectIdentifier oid = (DERObjectIdentifier) it.next();
         
         if ( !ht.containsKey(oid) ){
           Vector valueList = getX509NameFields(x509Name, oid);
     
           //-- Only add the OID if has not null value
           if ( valueList != null ){
             Iterator itVals = valueList.iterator();
             
             while( itVals.hasNext() ){
               Object value = itVals.next();
               ht.put(oid, value);
               newOrdering.add(oid);
               newValues.add(value);
             }
             
           }
         }
       }
       
       Vector allOids    = x509Name.getOIDs();
       
       logger.debug("Add unespected fields to the end: ");
       //-- Add unespected fields to the end
       for ( int i=0; i<allOids.size(); i++ ) {
         
         DERObjectIdentifier oid = (DERObjectIdentifier) allOids.get(i);
         
 
         if ( !ht.containsKey(oid) ){
           Vector valueList = getX509NameFields(x509Name, oid);
     
           //-- Only add the OID if has not null value
           if ( valueList != null ){
             Iterator itVals = valueList.iterator();
             
             while( itVals.hasNext() ){
               Object value = itVals.next();
               ht.put(oid, value);
               newOrdering.add(oid);
               newValues.add(value);
               logger.debug("added --> " + oid + " val: " + value);
             }
             
           }
         }
         
       } 
       
       //-- Create X509Name with the ordered fields
       X509Name orderedName = new X509Name(newOrdering, newValues);
       logger.debug("[getOrderedX509Name.retorna]:: " + orderedName);
       
       return orderedName;
       
     }
 
     /*
      * Obtiene el valor para un campo DN a partir de un X509Name, o nulo si
      *  el campo no existe.
      * 
      * @param name
      * @param id
      * @return
      */
     private static Vector getX509NameFields( X509Name name, DERObjectIdentifier id ){
       
       Vector oids = name.getOIDs();
       Vector values = name.getValues();
       Vector vRet = null;
       
       for ( int i=0; i<oids.size(); i++ ){
         
         if ( id.equals(oids.elementAt(i)) ){
           if ( vRet == null ){ vRet = new Vector(); }
           vRet.add(values.get(i));
         }
         
       }
       
       return vRet;
       
     }
 
 
 }